import numpy as np
import pandas as pd
import os
import copy
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn import preprocessing
from sklearn import svm
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
import matplotlib.pyplot as plt
import csv

path = r'./50城市三年气象数据'

class MeteolPr():
    def __init__(self, path):
        self.f0 = os.path.join(path, 'position.xlsx')
        self.f1 = os.path.join(path,'空气质量')
        self.f2 = os.path.join(path,'逐日实况')
        # 50城市排序列表
        self.site = self.setAdIndex()
        # 数据初始化字典，日期为key值；在将进行readerCsv()操作之后，将记录合并后的数据
        self.timeDict = dict(self.setTimeList())
        # 排序后的日期索引key值列表
        self.keys = sorted(self.timeDict.keys())
        # 空气质量表缺失表天数
        self.defaut = list(self.timeDict.keys())
        # -1 表示内地；-2 表示临海
        self.positions = pd.read_excel(self.f0, header=None).to_numpy()
        # self.airQ = ['优', '良', '轻度污染', '中度污染', '重度污染', '严重污染']

        # self.deal_default()

    def setTimeList(self):
        timeList = []
        for year in range(2017, 2020):
            for month in range(1, 13):
                if month in [1, 3, 5, 7, 8, 10, 12]:
                    if month < 10:
                        month = '0' + str(month)
                    else:
                        month = str(month)
                    for day in range(1, 32):
                        if day < 10:
                            day = '0' + str(day)
                        else:
                            day = str(day)
                        timeList.append((str(year) + month + day, None))
                elif month == 2:
                    month = '0' + str(month)
                    for day in range(1, 29):
                        if day < 10:
                            day = '0' + str(day)
                        else:
                            day = str(day)
                        timeList.append((str(year) + month + day, None))
                else:
                    if month < 10:
                        month = '0' + str(month)
                    else:
                        month = str(month)
                    for day in range(1, 31):
                        if day < 10:
                            day = '0' + str(day)
                        else:
                            day = str(day)
                        timeList.append((str(year) + month + day, None))
        return timeList

    def setAdIndex(self):
        address = []
        dff = pd.read_csv(self.f2 + '\\' + os.listdir(self.f2)[0]).sort_values(by='市')
        for province, city in zip(dff.iloc[:, 0].to_list(), dff.iloc[:, 1].to_list()):
            address.append([province, city])
        return address

    def dealAdDdefaut(self, dff, file, data1):
        num = 50 - dff.shape[0]
        while num > 0:
            num -= 1
            key = int(file[:8])
            cities = dff.iloc[:, 1].to_list()
            for i in range(50):
                if cities[i][:2] not in self.site[i][1]:
                    invalue = 0
                    if key % 100 > 2:
                        key1 = str(key - 1)
                        key2 = str(key - 2)
                    elif key % 100 == 2:
                        key1 = str(key - 1)
                        if round(key % 10000) == 2:
                            key2 = str(key)[:6] + '28'
                        elif round(key % 10000) in [1, 3, 5, 7, 8, 10, 12]:
                            key2 = str(key)[:6] + '31'
                        else:
                            key2 = str(key)[:6] + '30'
                    else:
                        if round(key % 10000) == 2:
                            key1 = str(key)[:6] + '28'
                            key2 = str(key)[:6] + '27'
                        elif round(key % 10000) in [1, 3, 5, 7, 8, 10, 12]:
                            key1 = str(key)[:6] + '31'
                            key2 = str(key)[:6] + '30'
                        else:
                            key1 = str(key)[:6] + '30'
                            key2 = str(key)[:6] + '29'
                    keyValue1 = data1.get(key1)
                    keyValue2 = data1.get(key2)
                    if keyValue1 is not None and keyValue2 is not None:
                        invalue = (keyValue1[i] - keyValue2[i]) * 0.6 + keyValue1[i]
                    elif keyValue1 is not None and keyValue2 is None:
                        invalue = keyValue1[i]
                    elif keyValue1 is None and keyValue2 is not None:
                        invalue = keyValue2[i]
                    dff.loc[50 - num] = [self.site[i][0], self.site[i][1], invalue]
                    # print(keyValue2[i],keyValue1[i], round(invalue)))
                    dff = dff.sort_values(by=1)
                    break
        return dff

    def readCsv(self):
        revise = []
        data1 = copy.deepcopy(self.timeDict)
        for file in os.listdir(self.f2):
            arr = pd.read_csv(self.f2 + '\\' + file).sort_values(by='市').iloc[:, 2:].to_numpy()
            arr = self.wether2standard(arr, revise)
            if len(revise) < 3:
                revise.insert(0, arr)
            else:
                revise[2] = revise[1]
                revise[1] = revise[0]
                revise[0] = arr
            self.timeDict.update({file[:8]: arr})
        data2 = copy.deepcopy(self.timeDict)
        for file in os.listdir(self.f1):
            dff = pd.read_csv(self.f1 + '\\' + file, header=None).sort_values(by=1)
            if dff.shape[0] == 50:
                arr = dff.iloc[:, 2].to_numpy()
            elif dff.shape[0] < 50:
                arr = self.dealAdDdefaut(dff, file, data1).iloc[:, 2].to_numpy()
            else:
                raise Exception('Number of cities out of bound.')
            arr = self.nums2rank(arr, [50, 100, 150, 200, 300])
            data1.update({file[:8]: arr})
            if data2.get(file[:8]) is not None:
                self.defaut.remove(file[:8])
                self.timeDict.update({file[:8]: np.column_stack((data2.get(file[:8]), arr))})
        return data1, data2

    def wether2standard(self, arr, revise):
        for i in range(50):
            for j in range(7):
                if arr[i][j] != 999999:
                    if j == 2:
                        arr[i][j] = arr[i][j] - 1001
                    elif j == 4:
                        arr[i][j] = int(arr[i][j] / 90) % 4
                    elif j == 6:
                        arr[i][j] = self.num2rank(arr[i][j], [50, 200, 500, 1000, 2000, 4000, 10000])
        for i in range(50):
            for j in range(7):
                flag = True
                if arr[i][j] == 999999:
                    if len(revise) > 0:
                        for item in revise:
                            if item[i][j] == 999999:
                                continue
                            else:
                                arr[i][j] = item[i][j]
                                flag = False
                                break
                    if flag:
                        site_index = self.positions[i]
                        citys = list(site_index)
                        new_value = 0
                        for num in site_index:
                            if num < 0 or arr[num][j] == 999999:
                                citys.remove(num)
                        if len(citys) > 0:
                            for index in citys:
                                new_value += arr[index][j]
                                arr[i][j] = new_value / len(citys)
                        else:
                            raise Exception('999999')
        return arr

    def nums2rank(self, nums, ranks, pos=True, flag=True):
        result = []
        for num in nums:
            result.append(self.num2rank(num, ranks, pos, flag))
        return result

    def num2rank(self, num, ranks, pos=True, flag=True):
        if flag:
            if pos and num < 0:
                raise Exception('num is negative.')
            n = 0
            if num < ranks[0]:
                return 0
            elif num >= ranks[-1]:
                return len(ranks)
            while n < len(ranks):
                n = n + 1
                if ranks[n - 1] <= num < ranks[n]:
                    return n
        else:
            return round(num / ranks[0])

    def split_data(self):
        x_datas = []
        y_datas = []
        for i in range(50):
            x_datas.append([])
            y_datas.append([])
        init_list = list(map(self.timeDict.get, self.keys))
        for i in range(len(init_list)):
            for j in range(50):
                x_datas[j].append(init_list[i][j])
                y_datas[j].append(init_list[i][j][6:])
        return x_datas, y_datas

    # 数据清洗--转化为模型输入数据
    def weather_data(self, seq, flag=True):
        x_data = []
        y_data = []
        x_datas, y_datas = self.split_data()
        lenth = len(x_datas[seq])
        count = 0
        for index in range(9, lenth):
            if count > 0:
                count -= 1
                continue
            if self.keys[index + 13] in self.defaut:
                count = 13
                n = 1
                while self.keys[index + 13 + n] in self.defaut:
                    count += 1
                    n += 1
                continue
            if index + 14 == lenth:
                break
            x_data.append(x_datas[seq][index:index + 7])
            if flag:
                direction = x_data[-1][-1][4]
                cnum = self.positions[seq][direction]
                if cnum < 0:
                    aday = x_datas[seq][index + 7]
                else:
                    aday = x_datas[cnum][index + 7]
                x_data[-1].append(aday)
            y_data.append(y_datas[seq][index + 7:index + 14])
        ys = [[[], []], [[], []], [[], []], [[], []], [[], []], [[], []], [[], []]]
        for days in y_data:
            for i in range(7):
                ys[i][0].append(days[i][0])
                ys[i][1].append(days[i][1])
        xs = []
        for days in x_data:
            day7 = []
            for day in days:
                day7.extend(list(day[0:4]))
                day7.extend(list(day[5:]))
            xs.append(day7)
        # print(len(xs),len(ys[0][0]))
        return xs, ys

    def select_model(self):
        total_rf = None
        total_svm = None
        total_bp = None
        total_adb = None
        labels = ['RandomFores', 'svm', 'bp', 'adaboost']
        for index in range(50):
            # xs, ys = self.weather_data(index)  # 引入地理位置矩阵
            xs, ys = self.weather_data(index,flag=False)   # flag=False 不引入矩阵
            x_data = xs
            # x_data = preprocessing.scale(xs)
            result = None
            for j in range(7):
                y_data = ys[j][1]
                x_train, x_test, y_train, y_test = train_test_split(x_data, y_data, test_size=0.3)
                model1 = RandomForestClassifier(n_estimators=3000)
                model1.fit(x_train, y_train)
                model2 = svm.SVC(gamma=0.1, kernel='linear')
                model2.fit(x_train, y_train)
                model3 = MLPClassifier(hidden_layer_sizes=(1000,), max_iter=2000)
                model3.fit(x_train, y_train)
                model4 = AdaBoostClassifier(
                    DecisionTreeClassifier(max_depth=4, min_samples_split=2, min_samples_leaf=6),
                    algorithm="SAMME", n_estimators=1000, learning_rate=0.01)
                model4.fit(x_train, y_train)
                predict1 = model1.predict(x_test)
                predict2 = model2.predict(x_test)
                predict3 = model3.predict(x_test)
                predict4 = model4.predict(x_test)
                accu1 = accuracy_score(y_test, predict1)
                accu2 = accuracy_score(y_test, predict2)
                accu3 = accuracy_score(y_test, predict3)
                accu4 = accuracy_score(y_test, predict4)
                if result is None:
                    result = np.array([accu1, accu2, accu3, accu4])
                else:
                    result = np.vstack((result, [accu1, accu2, accu3, accu4]))
            for item in result:
                print(item)
            xx = np.arange(7)
            for i in range(4):
                plt.plot(xx, result[:, i], label=labels[i])
            plt.legend()
            plt.xlabel('date_predict')
            plt.ylabel('accuracy')
            plt.savefig('./img0_1/compare_models{}.jpg'.format(index), dpi=300)
            plt.cla()
            if total_rf is None:
                total_rf = result[:, 0]
                total_svm = result[:, 1]
                total_bp = result[:, 2]
                total_adb = result[:, 3]
            else:
                total_rf = np.vstack((total_rf, result[:, 0]))
                total_svm = np.vstack((total_svm, result[:, 1]))
                total_bp = np.vstack((total_bp, result[:, 2]))
                total_adb = np.vstack((total_adb, result[:, 3]))
        xx = np.arange(50)
        total_rf = total_rf.T
        total_svm = total_svm.T
        total_bp = total_bp.T
        total_adb = total_adb.T
        with open('total_rf_1.csv', 'w', newline='') as hr:
            writer = csv.writer(hr)
            writer.writerows(total_rf)
        for i in range(7):
            plt.plot(xx, total_rf[i], label=labels[0])
            plt.plot(xx, total_svm[i], label=labels[1])
            plt.plot(xx, total_bp[i], label=labels[2])
            plt.plot(xx, total_adb[i], label=labels[3])
            plt.legend()
            plt.xlabel('city_seq')
            plt.ylabel('accuracy')
            plt.savefig('./img1_1/city_compare_models{}.jpg'.format(i), dpi=300)
            plt.cla()
        mean_rf = np.mean(total_rf, axis=0)
        mean_svm = np.mean(total_svm, axis=0)
        mean_bp = np.mean(total_bp, axis=0)
        mean_adb = np.mean(total_adb, axis=0)
        plt.plot(xx, mean_rf, label=labels[0])
        plt.plot(xx, mean_svm, label=labels[1])
        plt.plot(xx, mean_bp, label=labels[2])
        plt.plot(xx, mean_adb, label=labels[3])
        plt.legend()
        plt.xlabel('city_seq')
        plt.ylabel('accuracy')
        plt.savefig('./img1_1/city_mean_models{}.jpg'.format(i), dpi=300)
        plt.close()

op = MeteolPr(path)
op.readCsv()
op.select_model()